﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Physis.Security.Providers {

    public class WindowsIntegrated : ISecurityProvider {

        #region Private Properies 
        
        private Exception lastException = null;

        private System.Diagnostics.TraceSwitch traceSwitchSecurity = new System.Diagnostics.TraceSwitch ("Security", "Messages related to the Enterprise and Security");

        #endregion 


        #region Properties

        public Exception LastException { get { return lastException; } }

        #endregion


        #region Public Methods

        public AuthenticationResponse Authenticate (System.Security.Principal.IIdentity identity) {

            AuthenticationResponse response = new AuthenticationResponse ();

            System.Security.Principal.WindowsIdentity windowsIdentity;

            System.Security.Principal.NTAccount userAccount;


            // WHEN USING WINDOWS INTEGRATED  (NEGOTIATED) AUTHENTICATION, THE CALLING IDENTITY IS

            // IN THE Thread.CurrentPrincipal WHILE THE ASP.NET HOST IDENTITY IS IN THE System.Security.Principal
            
            if (traceSwitchSecurity.TraceVerbose) {

                System.Diagnostics.Trace.WriteLine ("\r\n[Physis.Core.Enterprise.Security.Provider.WindowsIntegrated] Current Credentials");

                System.Diagnostics.Trace.WriteLine ("Identity: " + identity.Name + " [" + identity.AuthenticationType + "] ");

                System.Diagnostics.Trace.WriteLine ("Thread Identity Principal: " + System.Threading.Thread.CurrentPrincipal.Identity.Name + " [" + System.Threading.Thread.CurrentPrincipal.Identity.AuthenticationType + "]");

                System.Diagnostics.Trace.WriteLine ("System Security Principal: " + System.Security.Principal.WindowsIdentity.GetCurrent ().Name);

            }


#if DEBUG 
            
            System.Diagnostics.Debug.WriteLine ("\r\n[Physis.Core.Enterprise.Security.Provider.WindowsIntegrated] Current Credentials");

            System.Diagnostics.Debug.WriteLine ("Identity: " + identity.Name + " [" + identity.AuthenticationType + "] ");

            System.Diagnostics.Debug.WriteLine ("Thread Identity Principal: " + System.Threading.Thread.CurrentPrincipal.Identity.Name + " [" + System.Threading.Thread.CurrentPrincipal.Identity.AuthenticationType + "]");

            System.Diagnostics.Debug.WriteLine ("System Security Principal: " + System.Security.Principal.WindowsIdentity.GetCurrent ().Name);

#endif 


            switch (identity.AuthenticationType.ToLower ()) {

                case "ntlm":

                case "kerberos":

                case "negotiate":

                    try {

                        windowsIdentity = (System.Security.Principal.WindowsIdentity)identity;

                        userAccount = new System.Security.Principal.NTAccount (windowsIdentity.Name);

                        response.DomainName = ((userAccount.Value.Split ('\\').Length > 1) ? userAccount.Value.Split ('\\')[0] : String.Empty);

                        response.UserAccountName = userAccount.Value.Split ('\\')[userAccount.Value.Split ('\\').Length - 1];

                        response.UserDisplayName = UserDisplayName (response.DomainName, response.UserAccountName);

                        if (String.IsNullOrWhiteSpace (response.UserDisplayName)) { response.UserDisplayName = response.UserAccountName; }


                        // BELOW IS AN EXAMPLE IF WE WANT TO PULL THE GROUP MEMBERSHIP IN FROM THE SECURITY PROVIDER 

                        // BUT IN THIS CASE, THE MEMBERSHIP GOES INTERNAL TO THE APPLICATION AND APPLICATION ROLES ON A PER USER BASIS 

                        //foreach (System.Security.Principal.IdentityReference currentGroup in windowsIdentity.Groups) {

                        //    groupAccount  = (System.Security.Principal.NTAccount) currentGroup.Translate (typeof (System.Security.Principal.NTAccount));

                        //    System.Diagnostics.Debug.WriteLine (currentGroup.Value);

                        //}                        


                        response.IsAuthenticated = identity.IsAuthenticated;

                    }

                    catch (Exception exception) {

                        response.AuthenticationError = Enumerations.AuthenticationError.OtherUndefinedError;

                        response.LastException = exception;

#if DEBUG 

                        System.Diagnostics.Trace.WriteLine ("Authentication Error: " + response.AuthenticationError.ToString ());

                        System.Diagnostics.Trace.WriteLine ("Authentication Exception: " + response.LastExceptionMessage);

#endif 

                    }


                    break;

                default:

                    response.AuthenticationError = Enumerations.AuthenticationError.SecurityProviderError;

                    response.LastException = new Exception ("Invalid Authentication Type for Provider [WindowsIntegrated]: " + identity.AuthenticationType);

                    break;

            }

            if ((traceSwitchSecurity.TraceVerbose) || ((traceSwitchSecurity.TraceError) && (response.AuthenticationError != Enumerations.AuthenticationError.NoError))) {

                // LOG IF VERBOSE OR AN ERROR OCCURED 

                System.Diagnostics.Trace.WriteLine ("Authentication Error: " + response.AuthenticationError.ToString ());

                System.Diagnostics.Trace.WriteLine ("Authentication Exception: " + response.LastExceptionMessage);

            }

            return response;

        }

        public AuthenticationResponse Authenticate () {

            return Authenticate (System.Threading.Thread.CurrentPrincipal.Identity);

        }


        public String UserDisplayName (String domainName, String userAccountName) {

            String userDisplayName = String.Empty;

            System.DirectoryServices.DirectoryEntry directoryEntry;


            try {

                // CONNECT TO ROOT 

                directoryEntry = new System.DirectoryServices.DirectoryEntry ("WinNT://" + domainName);

                // CONNECT TO USER 

                directoryEntry = new System.DirectoryServices.DirectoryEntry (directoryEntry.Path + "/" + userAccountName); 

                if (directoryEntry != null) {

                    // USE THE HELPER TO GET THE PROPERTIES 

                    DirectoryEntryHelper directoryEntryHelper = new DirectoryEntryHelper (directoryEntry);

                    userDisplayName = directoryEntryHelper.DisplayName;

                }

            }

            catch (Exception exception) {

                if ((traceSwitchSecurity.TraceVerbose) || (traceSwitchSecurity.TraceError)) {

                    // LOG IF VERBOSE OR AN ERROR OCCURED 

                    System.Diagnostics.Trace.WriteLine ("[WindowsIntegrated.UserDisplayName.DirectoryServices (" + domainName + ", " + userAccountName + ")]: " + exception.Message);

                }

            }

            return userDisplayName;

        }

        public List<DirectoryEntryHelper> Users (String domainName, String serverName) {

            List<DirectoryEntryHelper> users = new List<DirectoryEntryHelper> ();

            System.DirectoryServices.DirectoryEntry directoryRoot;


            try {

                // CONNECT TO ROOT 

                directoryRoot = new System.DirectoryServices.DirectoryEntry ("WinNT://" + domainName + ((!String.IsNullOrWhiteSpace (serverName) ? "/" + serverName : String.Empty))); // THIS GRABS THE USER GROUP 

                // DIRECTORY SEARCHER IS NOT VALID WITH "WINNT" PROTOCOL

                directoryRoot.Children.SchemaFilter.Add ("User");

                foreach (System.DirectoryServices.DirectoryEntry currentEntry in directoryRoot.Children) {

                    if ((currentEntry.SchemaClassName.Equals ("Group")) || (currentEntry.SchemaClassName.Equals ("User"))) {

                        DirectoryEntryHelper user = new DirectoryEntryHelper (currentEntry);

                        users.Add (user);

                    }

                }

            }

            catch (Exception exception) {

                if ((traceSwitchSecurity.TraceVerbose) || (traceSwitchSecurity.TraceError)) {

                    // LOG IF VERBOSE OR AN ERROR OCCURED 

                    System.Diagnostics.Trace.WriteLine ("[WindowsIntegrated.Users.DirectoryServices (" + domainName + ")]: " + exception.Message);

                }

            }

            return users;

        }
        
        #endregion 

    }

}
